package com.ruoyi.framework.shiro.realm;

import cn.hutool.core.lang.TypeReference;
import cn.hutool.core.net.url.UrlBuilder;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.http.HttpResponse;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import com.google.common.collect.Maps;
import com.ruoyi.common.core.domain.entity.SysUser;
import com.ruoyi.framework.shiro.auth.CustomizedToken;
import com.ruoyi.framework.shiro.service.CommonAuthorizationService;
import com.ruoyi.framework.shiro.util.EncryptUtils;
import com.ruoyi.framework.shiro.util.SignBuilder;
import com.ruoyi.system.domain.SysThird;
import com.ruoyi.system.service.ISysThirdService;
import org.apache.http.entity.ContentType;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;

import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

/**
 * @author renzhihao
 * @date 2023/11/29 13:46
 */
public class ThirdRealm extends AuthorizingRealm {
    private static final Logger log = LoggerFactory.getLogger(ThirdRealm.class);

    @Autowired
    private CommonAuthorizationService commonAuthorizationService;

    @Autowired
    private ISysThirdService thirdService;

    @Override
    protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        return commonAuthorizationService.getAuthorizationInfo();
    }

    @Override
    protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        CustomizedToken customizedToken = (CustomizedToken) token;
        int credentials;

        Map<String, Object> profile = Maps.newHashMap();
        String username;
        if (Objects.nonNull(customizedToken.getSoapMessage())) {
            username = customizedToken.getUsername();
            credentials = username.hashCode();
            profile = customizedToken.getSoapMessage();
        } else {
            String code = customizedToken.getCode();
            profile = getProfileByCode(code);
            log.info("请求ID: {}, 客户端ID: {}, 本次认证账号: {}, 本次认证ID: {}, 当前CAS登录账号: {}," +
                            "当前CAS登录账号ID: {}, 认证方式: {}",
                    profile.get("requestId"),
                    profile.get("clientId"),
                    profile.get("authAccount"),
                    profile.get("authAccountId"),
                    profile.get("account"),
                    profile.get("accountId"),
                    profile.get("authType"));
            username = (String) profile.get("authAccount");
            credentials = code.hashCode();
        }

        SysUser user = commonAuthorizationService.getAndAddUserIfNotPresent(username);
        user.setThirdProfile(profile);

        return new SimpleAuthenticationInfo(user, credentials, getName());
    }


    @Override
    public boolean supports(AuthenticationToken token) {
        return token instanceof CustomizedToken;
    }


    public Map<String, Object> getProfileByCode(String code) {
        SysThird third = thirdService.selectThirdAuth();
        String host = third.getServerUrl();
        String path = third.getProfileUrl();
        String clientId = third.getClientId();
        String clientSecret = third.getClientSecret();
        String encType = third.getEncType();
        boolean isSign = Objects.equals(third.getSign(), 0);
        boolean isEnc = Objects.equals(third.getEnc(), 0);

        String accessToken = getAccessToken();

        Map<String, Object> bodyMap = new HashMap<>();
        Map<String, Object> paramMap = new HashMap<>();
        bodyMap.put("token", code);

        paramMap.put("clientId", clientId);

        if (isSign) { // 加签
            long currTime = System.currentTimeMillis();
            paramMap.put("timestamp", String.valueOf(currTime));

            String sign = SignBuilder.create(clientId, clientSecret)
                    .requestPath(path)
                    .requestMethod("POST")
                    .params(paramMap)
                    .sign();
            paramMap.put("sign", sign);
            bodyMap.put("sign", sign);
        }

        String body = JSONUtil.toJsonStr(bodyMap);
        if (isEnc) {
            body = EncryptUtils.encrypt(body, clientSecret, encType);
        }

        String url = UrlBuilder.of(host).addPath(path)
                .setQuery(UrlQuery.of(paramMap))
                .toString();

        HttpResponse response = HttpUtil.createPost(url).body(body, ContentType.APPLICATION_JSON.getMimeType())
                .bearerAuth(accessToken).execute();

        if (!response.isOk()) {
            log.info("获取用户信息失败");
            throw new AuthenticationException("获取用户信息失败");
        }

        String result = response.body();
        Map<String, Object> resultMap = JSONUtil.toBean(result, new TypeReference<>() {
        }, true);
        if (Objects.equals(resultMap.get("code"), 1000)) {
            String data;
            if (isEnc) {
                data = EncryptUtils.decrypt((String) resultMap.get("data"), clientSecret, encType);
            } else {
                data = JSONUtil.toJsonStr(resultMap.get("data"));
            }
            return JSONUtil.toBean(data, new TypeReference<>() {
            }, true);
        }

        log.info("获取用户信息失败, 错误消息：{}", resultMap.get("msg"));
        throw new AuthenticationException("获取用户信息失败");
    }

    public static void main(String[] args) {
        String s = "61WbmNUV37HB2nXAuThi/sOO+pVlfP&p4v+cMH6mnps0=";
        String encode = URLEncoder.encode(s, StandardCharsets.UTF_8);
        Map<String, Object> paramMap = Map.of(
                "clientSecret", s
        );
        String url = UrlBuilder.of("http://127.0.0.1:8100").addPath("abc")
                .setQuery(UrlQuery.of(paramMap))
                .toString();

        System.out.println(encode);
        System.out.println(url);
    }

    private String getAccessToken() {
        SysThird third = thirdService.selectThirdAuth();
        String host = third.getServerUrl();
        String path = third.getTokenUrl();
        String clientId = third.getClientId();
        String clientSecret = third.getClientSecret();
        String encType = third.getEncType();
        boolean isSign = Objects.equals(third.getSign(), 0);
        boolean isEnc = Objects.equals(third.getEnc(), 0);
        String encodeClientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8);
        String encodeClientSecret = URLEncoder.encode(clientSecret, StandardCharsets.UTF_8);

        Map<String, Object> bodyMap = new HashMap<>();
        Map<String, Object> paramMap = new HashMap<>();

        paramMap.put("clientId", encodeClientId);
        if (!isSign) { // 不加签
            paramMap.put("clientSecret", encodeClientSecret);
        } else { // 加签
            long currTime = System.currentTimeMillis();
            paramMap.put("timestamp", String.valueOf(currTime));

            String sign = SignBuilder.create(clientId, clientSecret)
                    .requestPath(path)
                    .requestMethod("POST")
                    .params(paramMap)
                    .sign();
            paramMap.put("sign", sign);
            bodyMap.put("sign", sign);
        }

        String body = JSONUtil.toJsonStr(bodyMap);
        if (isEnc) {
            body = EncryptUtils.encrypt(body, clientSecret, encType);
        }

        String url = UrlBuilder.of(host, null).addPath(path)
                .setQuery(UrlQuery.of(paramMap))
                .toString();

        HttpResponse response = HttpUtil.createPost(url)
                .body(body, ContentType.APPLICATION_JSON.getMimeType()).execute();
        if (!response.isOk()) {
            log.info("获取accessToken失败");
            throw new AuthenticationException("获取accessToken失败");
        }

        String result = response.body();
        Map<String, Object> resultMap = JSONUtil.toBean(result, new TypeReference<>() {
        }, true);
        if (Objects.equals(resultMap.get("code"), 1000)) {
            String data;

            if (isEnc) {
                data = EncryptUtils.decrypt((String) resultMap.get("data"), clientSecret, encType);
            } else {
                data = JSONUtil.toJsonStr(resultMap.get("data"));
            }

            Map<String, Object> dataMap = JSONUtil.toBean(data, new TypeReference<>() {
            }, true);
            return (String) dataMap.get("token");
        }

        log.info("获取accessToken失败, 错误消息：{}", resultMap.get("msg"));
        throw new AuthenticationException("获取accessToken失败");
    }
}
